import * as THREE from 'three';
import { Object3D } from "three"
import Stats from 'three/examples/jsm/libs/stats.module';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

import {Thing} from './thing'
import {WaterPlane} from './water'
import { TWEEN } from "three/examples/jsm/libs/tween.module.min.js";
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { CSS2DObject, CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer'

import { CarInfoType } from '../types'
import { CarColor } from '../types';
import { Sun } from './sun'

import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment';
import { SkyThing } from './sky';
import { SceneTime } from './SceneTime';


const licenceList = ['长相思兮长相忆', '短相思兮无穷极', '相思相见知何日', '此时此夜难为情', '何处春江无月明', '滟滟随波千万里', '金风玉露一相逢', '便胜却人间无数']
const colorList = [CarColor.BLUE, CarColor.GREEN, CarColor.YELLOW, CarColor.BLUE, CarColor.GREEN, CarColor.BLUE, CarColor.GREEN]

export class BridgeScene {

    time: SceneTime;

    parameters:any = null;
    container: HTMLElement;

    stats: any;

    camera: THREE.PerspectiveCamera | null = null;
    scene: THREE.Scene;
    renderer: THREE.WebGLRenderer;

    labelRenderer: CSS2DRenderer | null = null;

    pmremGenerator: THREE.PMREMGenerator
    renderTarget: THREE.WebGLRenderTarget

    onWindowResize: any;
    onWindowMousedown: any;

    sun: Sun;

    things: Array<Thing> = [];

    loader: GLTFLoader;

    carModels: Array<Object3D> = [];

    // 把车子放进这个组内
    carGroup: THREE.Group;

    linePosition = [{
        bigin: {
            x: -1.5,
            y: 22.4,
            z: -320
        },
        mid: {
            x: -1.5,
            y: 22.4,
            z: -130
        },
        end: {
            x: -1.5,
            y: 22.4,
            z: 60
        }
    }, {
        bigin: {
            x: 1.5,
            y: 22.4,
            z: 60
        },
        mid: {
            x: 1.5,
            y: 22.4,
            z: -130
        },
        end: {
            x: 1.5,
            y: 22.4,
            z: -320
        }
    }];

    CarHashMap = [{
        黄: 'url(./chepai/小型车黄牌.png)',
        绿: 'url(./chepai/小型车绿牌.png)',
        蓝: 'url(./chepai/小型车蓝牌.png)',
    }, {
        黄: 'url(./chepai/中型车黄牌.png)',
        绿: 'url(./chepai/中型车绿牌.png)',
        蓝: 'url(./chepai/中型车蓝牌.png)',
    }, {
        黄: 'url(./chepai/大型车黄牌.png)',
        绿: 'url(./chepai/大型车绿牌.png)',
        蓝: 'url(./chepai/大型车蓝牌.png)',
    }];

    constructor(el: HTMLElement, time: SceneTime) {
        this.container = el;
        this.time = time;
        // 太阳
        this.sun = new Sun(this.time);

        // 场景
        const scene = this.scene = new THREE.Scene();

        // loader
        const dracoLoader = new DRACOLoader()
        dracoLoader.setDecoderPath("./draco/")
        this.loader = new GLTFLoader()
        this.loader.setDRACOLoader(dracoLoader);

        // 渲染器
        const renderer = this.renderer = new THREE.WebGLRenderer({ antialias: true, logarithmicDepthBuffer: true });
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(this.container.offsetWidth, this.container.offsetHeight);
        renderer.toneMapping = THREE.ACESFilmicToneMapping;
        renderer.shadowMap.enabled = true
        renderer.shadowMap.type = THREE.VSMShadowMap
        this.container.appendChild(renderer.domElement);
        this.pmremGenerator = new THREE.PMREMGenerator(renderer);
    
        // renderTarget = pmremGenerator.fromScene(sky as any);
        const renderTarget = this.renderTarget = this.pmremGenerator.fromScene(new RoomEnvironment());
    
        this.scene.environment = renderTarget.texture;

        this.carGroup = new THREE.Group()
        this.scene.add(this.carGroup);
    }

    updateSceneTime(hour:number, minute: number, second: number) {
        this.time.hour = hour;
        this.time.minute = minute;
        this.time.second = second;
    }
 
    init() {
	    
        // 2d渲染器
        this.labelRenderer = new CSS2DRenderer()
        this.labelRenderer.setSize(this.container.offsetWidth, this.container.offsetHeight);
        this.labelRenderer.domElement.style.position = 'absolute';
        this.labelRenderer.domElement.style.top = '0';
        this.labelRenderer.domElement.style.pointerEvents = 'none';
        this.container.appendChild(this.labelRenderer.domElement);

        // 相机
        let camera = this.camera = new THREE.PerspectiveCamera(55, this.container.offsetWidth / this.container.offsetHeight, 1, 20000);
        camera.position.set(0, 40, -20);

        // 相机轨道控制器
        let controls: OrbitControls = new OrbitControls(camera, this.renderer.domElement);
        controls.maxPolarAngle = Math.PI * 0.495;
        // controls.enablePan = false
        controls.target.set(0, 10, -100);
        controls.maxDistance = 2000.0;
        controls.update();

        this.initSunWater();

        let that = this;

        const onWindowResize = this.onWindowResize = function() {
            camera.aspect = that.container.offsetWidth / that.container.offsetHeight;
            camera.updateProjectionMatrix();
            that.renderer.setSize(that.container.offsetWidth, that.container.offsetHeight);
        }
        window.addEventListener('resize', onWindowResize);

        const onWindowMousedown = this.onWindowMousedown = function(event:any) {
            const raycaster = new THREE.Raycaster();
            const pointer = new THREE.Vector2();

            pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
            pointer.y = - (event.clientY / window.innerHeight) * 2 + 1;

            raycaster.setFromCamera(pointer, camera)

            const intersect = raycaster.intersectObjects(that.carGroup.children) as any

            if (intersect.length > 0) {

                const flag = intersect[0].object.parent.getObjectByName('label').visible

                intersect[0].object.parent.getObjectByName('label').visible = !flag;

            }
        }
        window.addEventListener('mousedown', onWindowMousedown);

    }

    addThing(thing: Thing) {
        this.things.push(thing);
    }

    // 水 和 太阳
    async initSunWater() {
    
        if (this.scene) {
            // 加入水平面
            this.addThing(new WaterPlane(this.scene, this.sun));
            this.addThing(new SkyThing(this.scene, this.sun))
        }
        
        setInterval(()=> {
            this.updateSun();
        }, 500);
        

        // 性能监视器
        const stats = this.stats = Stats();
        this.container.appendChild(stats.dom);

        // 加载大桥的模型
        await this.loadBridge();

        // 加载车辆的模型
        await this.loadCarsModel();

        // 加载大飞机模型
        await this.loadPlaneModel();

        // 开启渲染
        this.animate();

        // 生成汽车
        this.generateCar();

    }

    // 加载大桥的模型
    loadBridge() {
        return new Promise((resolve, reject)=> {
            // 桥
            this.loader.load('./models/high_bridge.glb', gltf => {

                const model = gltf.scene
                model.scale.set(0.1, 0.1, 0.1)
                model.traverse((e: any) => {
                    if (e.isMesh) {
                        e.castShadow = true
                        e.receiveShadow = true
                        e.geometry.computeVertexNormals()
                    }
                })

                model.rotation.y = Math.PI / 2
                model.position.y -= 3

                const model1 = model.clone()
                model1.position.z -= 259
                model1.position.y -= 0.1
                model1.rotation.y = -Math.PI / 2

                if (this.scene) {
                    this.scene.add(model1)
                    this.scene.add(model)
                }
                resolve({
                    model: model,
                    model1: model1
                });
            })
        })
    }

    loadCarsModel() {

        return new Promise((resolve, reject)=> {

            // 车
            this.loader.load('./models/cars.glb', gltf => {

                const model = gltf.scene.children[0];

                model.traverse((e: any) => {
                    if (e.isMesh) {
                        e.castShadow = true
                        e.receiveShadow = true
                        e.geometry.computeVertexNormals()
                    }
                })

                this.carModels.push(model.children[1]);
                this.carModels.push(model.children[2]);
                this.carModels.push(model.children[3]);
                
                this.carModels.forEach(model=> {
                    model.scale.set(30, 30, 30)
                })

                
                resolve(model);
            })

        });

        
    }

    loadPlaneModel() {
        return new Promise((resolve, reject)=> {

            // 大飞机
            this.loader.load('./models/plane.glb', gltf => {

                const model = gltf.scene.children[0]

                model.traverse((e: any) => {
                    if (e.isMesh) {
                        e.castShadow = true
                        e.receiveShadow = true
                        e.geometry.computeVertexNormals()
                    }
                })

                model.scale.set(0.001, 0.001, 0.001)
                model.position.set(0, 60, -300)
                if (this.scene)
                    this.scene.add(model)

                resolve(model);

            })
        });
    } 

    animate() {
        requestAnimationFrame(()=> {
            this.animate();
        });
        this.render();
        this.stats.update();
    }

    render() {
        if (this.camera && this.scene && this.renderer && this.labelRenderer) {
            // 将所有物品更新一遍再渲染
            this.things.forEach(thing => {
                thing.update();
            });
            this.renderer.render(this.scene, this.camera);
            this.labelRenderer.render(this.scene, this.camera);
        }

    }

    updateSun() {
        this.sun.update();
    }

    generateCar() {
        // 
        setInterval(()=> {
            const beginTimeStamp = new Date().getTime()
            const middleTimeStamp = beginTimeStamp + 5000 + Math.random() * 2000
            const endTimeStamp = middleTimeStamp + 5000 + Math.random() * 2000
            const licence = licenceList[Math.ceil(Math.random() * 8)]
            const color = colorList[Math.ceil(Math.random() * 7)]
            
            const type = Math.floor(Math.random() * 3);
            const line = Math.floor(Math.random() * 2);

            this.beginTraffic({
                beginTimeStamp: beginTimeStamp,
                middleTimeStamp: middleTimeStamp,
                endTimeStamp: endTimeStamp,
                licence: licence,
                color: color,
                type: type,
                line: line
            });
        }, 5000);

        
    }

    beginTraffic(ins: CarInfoType) {

        let carType = ins.type;
        let model = this.carModels[carType];
        if (!model) {
            console.log(carType + '类型的模型不存在');
            return;
        }

        const car = model.clone() as THREE.Group
        const pos = this.linePosition[ins.line].bigin
        car.position.set(pos.x, pos.z, pos.z)
        if (ins.line === 1) car.rotation.y = Math.PI
    
        const position = {
            x: this.linePosition[ins.line].bigin.x,
            y: this.linePosition[ins.line].bigin.y,
            z: this.linePosition[ins.line].bigin.z,
        }
        let pointLabel: CSS2DObject
        const tween1 = new TWEEN.Tween(position)
    
        tween1.to(this.linePosition[ins.line].mid, ins.middleTimeStamp - ins.beginTimeStamp)
    
        tween1.onStart(() => {
    
            const img = this.CarHashMap[ins.type][ins.color]
            const innerHTML = ins.licence
            let labelDiv = createDiv(img, innerHTML)
    
            pointLabel = new CSS2DObject(labelDiv);
            pointLabel.visible = false
            pointLabel.name = 'label'
    
            car.add(pointLabel)
    
            this.carGroup.add(car)
    
        })
    
        tween1.onUpdate(() => {
            car.position.x = position.x
            car.position.y = position.y
            car.position.z = position.z
        })
    
        const tween2 = new TWEEN.Tween(position)
        tween2.to(this.linePosition[ins.line].end, ins.endTimeStamp - ins.middleTimeStamp)
        tween2.onUpdate(() => {
            car.position.x = position.x
            car.position.y = position.y
            car.position.z = position.z
        })
    
        tween2.onComplete(() => {
            car.remove(pointLabel)
            this.carGroup.remove(car)
        })
    
        tween1.chain(tween2).start()
    
    }

    clear() {

        if (this.onWindowResize) {
            window.removeEventListener('resize', this.onWindowResize)
        }
        if (this.onWindowMousedown) {
            window.removeEventListener('mousedown', this.onWindowMousedown);
        }
        if (this.labelRenderer) {
            document.body.removeChild(this.labelRenderer.domElement);
        }
        
    
        if (this.scene) {
            this.scene.traverse((child: any) => {
    
                if (child.isMesh) {
                    if (child.geometry) child.geometry.dispose();
                    if (child.material) child.material.dispose();
                    child.clear();
                }
            });
        }
    
        TWEEN && TWEEN.removeAll()
        if (this.scene) {
            this.scene.clear();
        }
        
        if (this.renderer) {
            this.renderer.dispose();
            this.renderer.forceContextLoss();
        }
        
        THREE.Cache.clear();
    
    }
    
}



function createDiv(imgUrl: string, innerHTML: string) {

	const labelDiv = document.createElement("div");
	labelDiv.style.width = '163px'
	labelDiv.style.height = '50px'
	labelDiv.style.backgroundImage = imgUrl
	labelDiv.style.textAlign = 'center'
	labelDiv.style.verticalAlign = 'center'
	labelDiv.style.color = '#ffffff'
	labelDiv.style.borderRadius = '25px'
	const div = document.createElement('div')
	div.innerHTML = innerHTML
	div.style.fontSize = '14px'
	div.style.width = '113px'
	div.style.marginLeft = '40px'
	div.style.height = '25px'
	div.style.lineHeight = '50px'
	labelDiv.appendChild(div)
	return labelDiv
}


